﻿using System;
using System.Collections.Generic;
using System.Linq;
using SurfaceCardsBoardLibrary.Interfaces;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows;
using Microsoft.Surface.Presentation.Controls;
using System.Windows.Controls;
using System.Windows.Media;
using SurfaceCardsBoardLibrary;
using Microsoft.Surface.Presentation;
using SurfaceCardsGameKit.Dispositions;
using System.Windows.Threading;
using System.Windows.Input;

namespace SurfaceCardsGameKit
{
    public class Deck : DependencyObject, IDeck
    {

        #region Private members

        private List<ICard> _cards;
        FanDisposition _fanDisposition;
        StackDisposition _stackDisposition;
        HeapDisposition _heapDisposition;
        bool _isUpdatingDisposition;
        bool _isInitializing = false;
        private double _defaultHeight;
        private double _defaultWidth;
        private DeckDisposition _deckDisposition;
        private bool _canApplyFan;
        private int _deferUpdateDispositionLevel;
        private bool _canReturnAllCards;
        private bool _isWaitingForCut;
        private bool _isWaitingForSplit;
        private bool _isWaitingForReturnCards;
        private bool _canMergeByUser;
        private Point _initialContactTurnCards;
        private int _lastCardReturnedIndex = -1;
        private CardsTurnDirection _cardsTurnDirection;

        #endregion

        #region IDeck Members

        private bool _autoRemoveWhenEmpty;
        IDisposition _disposition;
        private Rect _cardsDestinationRect;
        private bool _canRotate;
        private bool _canScale;
        private bool _canMove;
        private bool _canMoveByUser;
        private bool _canAddByUser;
        private bool _canRemoveByUser;
        private bool _canShuffle;
        private bool _isEyeEnabled;
        private bool _isMenuEnabled;
        private bool _isMosaicEnabled;
        private bool _isMosaicChecked;
        private double _mosaicWidth;
        private double _mosaicHeight;
        private CardInDeckPosition _cardAddingPosition;
        private CardInDeckFace _cardAddingFace;
        private CardInDeckFace _cardRemovingFace;
        DeckOrientation _deckOrientation;

        public bool AutoRemoveWhenEmpty
        {
            get
            {
                return this._autoRemoveWhenEmpty;
            }
            set
            {
                if (this._autoRemoveWhenEmpty != value)
                {
                    this._autoRemoveWhenEmpty = value;
                    CheckAutoRemoveWhenEmpty();
                    this.OnPropertyChanged("AutoRemoveWhenEmpty");
                }
            }
        }

        public IDisposition Disposition
        {
            get
            {
                return this._disposition;
            }
            set
            {
                if (this._disposition != value)
                {
                    this._disposition = value;
                    this.OnPropertyChanged("Disposition");
                }
            }
        }

        private bool _autosort;
        public bool AutoSort
        {
            get
            {
                return this._autosort;
            }
            set
            {
                if (this._autosort != value)
                {
                    this._autosort = value;
                    this.OnPropertyChanged("AutoSort");
                }
            }
        }

        private Comparison<ICard> _sortComparison;
        public Comparison<ICard> SortComparison
        {
            get
            {
                return this._sortComparison;
            }
            set
            {
                if (this._sortComparison != value)
                {
                    this._sortComparison = value;
                    this.OnPropertyChanged("SortComparison");
                }
            }
        }

        public void Sort()
        {
            if (this._sortComparison == null)
                throw new NullReferenceException("SortComparison must be defined");
            this._cards.Sort(this._sortComparison);
        }

        public void Shuffle()
        {
            Shuffle((int)DateTime.Now.Ticks);
        }

        public void Shuffle(int seed)
        {
            Random rnd = new Random(seed);

            double oldAngle = 0;

            if (this.Disposition is HeapDisposition)
            {
                foreach (Card card in this)
                    card.HeapDispositionOffset = null;

                oldAngle = ((HeapDisposition)this.Disposition).AngleRandom;
                ((HeapDisposition)this.Disposition).AngleRandom = 180;
            }


            using (this.DeferUpdateDisposition(true, true, 3))
            {
                for (int i = 0; i < this._cards.Count; i++)
                    this.Move(i, rnd.Next(this._cards.Count));
            }

            if (this.Disposition is HeapDisposition)
            {
                ((HeapDisposition)this.Disposition).AngleRandom = oldAngle;
            }
        }

        public void Move(int oldIndex, int newIndex)
        {
            ICard tmp = this._cards[oldIndex];
            this._cards.RemoveAt(oldIndex);
            this._cards.Insert(newIndex, tmp);
        }

        public bool CanRotate
        {
            get
            {
                return _canRotate;
            }
            set
            {
                if (_canRotate != value)
                {
                    this._canRotate = value;
                    this.OnPropertyChanged("CanRotate");
                }
            }
        }
        public bool CanScale
        {
            get
            {
                return _canScale;
            }
            set
            {
                if (_canScale != value)
                {
                    this._canScale = value;
                    this.OnPropertyChanged("CanScale");
                }
            }
        }
        public bool CanMove
        {
            get
            {
                return _canMove;
            }
            set
            {
                if (_canMove != value)
                {
                    this._canMove = value;
                    this.OnPropertyChanged("CanMove");
                }
            }
        }
        public bool CanMoveByUser
        {
            get
            {
                return _canMoveByUser;
            }
            set
            {
                if (_canMoveByUser != value)
                {
                    this._canMoveByUser = value;
                    this.OnPropertyChanged("CanMoveByUser");
                }
            }
        }
        public bool CanRemoveByUser
        {
            get
            {
                return _canRemoveByUser;
            }
            set
            {
                if (_canRemoveByUser != value)
                {
                    this._canRemoveByUser = value;
                    this.OnPropertyChanged("CanRemoveByUser");
                }
            }
        }
        public bool CanAddByUser
        {
            get
            {
                return _canAddByUser;
            }
            set
            {
                if (_canAddByUser != value)
                {
                    this._canAddByUser = value;
                    this.OnPropertyChanged("CanAddByUser");
                }
            }
        }
        public bool CanShuffle
        {
            get
            {
                return _canShuffle;
            }
            set
            {
                if (_canShuffle != value)
                {
                    this._canShuffle = value;
                    this.OnPropertyChanged("CanShuffle");
                }
            }
        }
        public bool CanMergeByUser
        {
            get { return _canMergeByUser; }
            set
            {
                if (_canMergeByUser != value)
                {
                    _canMergeByUser = value;
                    OnPropertyChanged("CanMergeByUser");
                }
            }
        }

        public bool IsEyeEnabled
        {
            get
            {
                return _isEyeEnabled;
            }
            set
            {
                if (_isEyeEnabled != value)
                {
                    this._isEyeEnabled = value;
                    this.OnPropertyChanged("IsEyeEnabled");
                }
            }
        }

        public bool IsMenuEnabled
        {
            get
            {
                return _isMenuEnabled;
            }
            set
            {
                if (_isMenuEnabled != value)
                {
                    this._isMenuEnabled = value;
                    this.OnPropertyChanged("IsMenuEnabled");
                }
            }
        }

        public bool IsMosaicEnabled
        {
            get
            {
                return _isMosaicEnabled;
            }
            set
            {
                if (_isMosaicEnabled != value)
                {
                    this._isMosaicEnabled = value;
                    this.OnPropertyChanged("IsMosaicEnabled");
                }
            }
        }

        public bool IsMosaicChecked
        {
            get
            {
                return _isMosaicChecked;
            }
            set
            {
                if (_isMosaicChecked != value)
                {
                    this._isMosaicChecked = value;
                    this.OnPropertyChanged("IsMosaicChecked");
                }
            }
        }

        public double MosaicWidth
        {
            get
            {
                return _mosaicWidth;
            }
            set
            {
                if (_mosaicWidth != value)
                {
                    this._mosaicWidth = value;
                    this.OnPropertyChanged("MosaicWidth");
                }
            }
        }

        public double MosaicHeight
        {
            get
            {
                return _mosaicHeight;
            }
            set
            {
                if (_mosaicHeight != value)
                {
                    this._mosaicHeight = value;
                    this.OnPropertyChanged("MosaicHeight");
                }
            }
        }

        public CardInDeckPosition CardAddingPosition
        {
            get { return _cardAddingPosition; }
            set
            {
                if (_cardAddingPosition != value)
                {
                    _cardAddingPosition = value;
                    OnPropertyChanged("CardAddingPosition");
                }
            }
        }

        public CardInDeckFace CardAddingFace
        {
            get { return _cardAddingFace; }
            set
            {
                if (_cardAddingFace != value)
                {
                    _cardAddingFace = value;
                    OnPropertyChanged("CardAddingFace");
                }
            }
        }

        public CardInDeckFace CardRemovingFace
        {
            get { return _cardRemovingFace; }
            set
            {
                if (_cardRemovingFace != value)
                {
                    _cardRemovingFace = value;
                    OnPropertyChanged("CardRemovingFace");
                }
            }
        }

        public DeckOrientation DeckOrientation
        {
            get { return _deckOrientation; }
            set
            {
                if (_deckOrientation != value)
                {
                    _deckOrientation = value;
                    OnPropertyChanged("DeckOrientation");
                }
            }
        }


        #endregion

        #region INotifyCollectionChanged

        private readonly SimpleMonitor _monitor;
        private const string CountString = "Count";
        private const string IndexerName = "Item[]";

        #endregion

        #region Constructor
        public Deck()
        {
            _isInitializing = true;

            _deferUpdateDispositionLevel = 0;
            this._monitor = new SimpleMonitor();
            this._cards = new List<ICard>();

            // instanciate dispositions and set the default one 
            _fanDisposition = new FanDisposition();
            _stackDisposition = new StackDisposition();
            _heapDisposition = new HeapDisposition();
            CanApplyFan = true;
            DeckDisposition = DeckDisposition.Heap;

            CanRotate = true;
            CanScale = true;
            CanMove = true;
            CanReturnAllCards = true;
            CanMergeByUser = true;

            CanAddByUser = true;
            CanRemoveByUser = true;
            CanMoveByUser = true;

            CardAddingFace = CardInDeckFace.AsIs;
            CardAddingPosition = CardInDeckPosition.Top;
            CardRemovingFace = CardInDeckFace.AsIs;
            DeckOrientation = DeckOrientation.Up;

            AutoSort = false;
            AutoRemoveWhenEmpty = true;

            IsEyeEnabled = true;
            IsMosaicEnabled = true;
            IsMenuEnabled = true;
            MosaicHeight = 100.0;
            MosaicWidth = 96.0;
        }
        #endregion

        #region Properties

        public Func<Rect> GetCardsDestinationRect { get; set; }
        public Action<Card, ContactEventArgs> CheckCaptureContactForCardsDelegate { get; set; }

        public bool CanReturnAllCards
        {
            get { return _canReturnAllCards; }
            set
            {
                if (_canReturnAllCards != value)
                {
                    _canReturnAllCards = value;
                    OnPropertyChanged("CanReturnAllCards");
                }
            }
        }

        public Size DefaultCardSize { get; set; }

        public DeckDisposition DeckDisposition
        {
            get { return _deckDisposition; }
            set
            {
                _deckDisposition = value;
                OnDeckDispositionChanged();
                OnPropertyChanged("DeckDisposition");
            }
        }

        public bool CanApplyFan
        {
            get { return _canApplyFan; }
            set
            {
                if (_canApplyFan != value)
                {
                    _canApplyFan = value;
                    OnCanApplyFanChanged();
                    OnPropertyChanged("CanApplyFan");
                }
            }
        }

        public Game Game { get; set; }

        #endregion

        #region Properties for DeckUC

        public Brush BorderBrush
        {
            get { return (Brush)GetValue(BorderBrushProperty); }
            set { SetValue(BorderBrushProperty, value); }
        }
        public static readonly DependencyProperty BorderBrushProperty =
            DependencyProperty.Register("BorderBrush", typeof(Brush), typeof(Deck), new UIPropertyMetadata(null));

        public Thickness BorderThickness
        {
            get { return (Thickness)GetValue(BorderThicknessProperty); }
            set { SetValue(BorderThicknessProperty, value); }
        }
        public static readonly DependencyProperty BorderThicknessProperty =
            DependencyProperty.Register("BorderThickness", typeof(Thickness), typeof(Deck), new UIPropertyMetadata(null));

        public CornerRadius CornerRadius
        {
            get { return (CornerRadius)GetValue(CornerRadiusProperty); }
            set { SetValue(CornerRadiusProperty, value); }
        }
        public static readonly DependencyProperty CornerRadiusProperty =
            DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(Deck), new UIPropertyMetadata(null));


        #region Shadow

        public Thickness ShadowMargin
        {
            get { return (Thickness)GetValue(ShadowMarginProperty); }
            set { SetValue(ShadowMarginProperty, value); }
        }
        public static readonly DependencyProperty ShadowMarginProperty =
            DependencyProperty.Register("ShadowMargin", typeof(Thickness), typeof(Deck), new UIPropertyMetadata(new Thickness(-5)));

        public CornerRadius ShadowCornerRadius
        {
            get { return (CornerRadius)GetValue(ShadowCornerRadiusProperty); }
            set { SetValue(ShadowCornerRadiusProperty, value); }
        }
        public static readonly DependencyProperty ShadowCornerRadiusProperty =
            DependencyProperty.Register("ShadowCornerRadius", typeof(CornerRadius), typeof(Deck), new UIPropertyMetadata(new CornerRadius(20)));

        public double ShadowOpacity
        {
            get { return (double)GetValue(ShadowOpacityProperty); }
            set { SetValue(ShadowOpacityProperty, value); }
        }
        public static readonly DependencyProperty ShadowOpacityProperty =
            DependencyProperty.Register("ShadowOpacity", typeof(double), typeof(Deck), new UIPropertyMetadata(0.5));

        public Visibility ShadowVisibility
        {
            get { return (Visibility)GetValue(ShadowVisibilityProperty); }
            set { SetValue(ShadowVisibilityProperty, value); }
        }
        public static readonly DependencyProperty ShadowVisibilityProperty =
            DependencyProperty.Register("ShadowVisibility", typeof(Visibility), typeof(Deck), new UIPropertyMetadata(Visibility.Collapsed));

        public Color ShadowColor
        {
            get { return (Color)GetValue(ShadowColorProperty); }
            set { SetValue(ShadowColorProperty, value); }
        }
        public static readonly DependencyProperty ShadowColorProperty =
            DependencyProperty.Register("ShadowColor", typeof(Color), typeof(Deck), new UIPropertyMetadata(Colors.Black));

        #endregion

        #endregion

        #region ICardContainer Members

        public void AddRange(IEnumerable<ICard> items)
        {
            this._cards.AddRange(items);
        }

        public void RemoveRange(IEnumerable<ICard> items)
        {
            foreach (var item in items)
                this._cards.Remove(item);
        }

        #endregion

        #region IList<ICard> Members

        public int IndexOf(ICard item)
        {
            return this._cards.IndexOf(item);
        }

        private void AddCard(ICard card)
        {
            AddCard(card, -1);
        }

        private void AddCard(ICard card, int index)
        {
            card.Parent = this;

            var c = card as Card;
            if (c != null)
                c.CheckCaptureContact = CheckCaptureContactForCard;

            switch (CardAddingFace)
            {
                case CardInDeckFace.FrontUp:
                    if (!card.IsFront)
                        card.Turn();
                    break;
                case CardInDeckFace.BackUp:
                    if (card.IsFront)
                        card.Turn();
                    break;
                case CardInDeckFace.AsIs:
                    // don't touch anything
                    break;
            }

            if (index == -1)
            {
                switch (CardAddingPosition)
                {
                    case CardInDeckPosition.Top:
                        index = 0;
                        this._cards.Insert(0, card);
                        break;
                    case CardInDeckPosition.Bottom:
                        index = this._cards.Count;
                        this._cards.Add(card);
                        break;
                }
            }
            else
                this._cards.Insert(index, card);

            OnPropertyChanged(CountString);
            OnPropertyChanged(IndexerName);
            OnCollectionChanged(NotifyCollectionChangedAction.Add, card, index);

            if (IsMosaicChecked)
            {
                card.Visibility = Visibility.Collapsed;
                UpdateDisposition(false);
            }
            else
                UpdateDisposition(true, true);
        }

        public void Insert(int index, ICard item)
        {
            AddCard(item, index);
        }

        public void RemoveAt(int index)
        {
            ICard card = this._cards[index];
            card.Parent = null;
            card.LastParent = this;
            card.IsSelected = false;

            Card c = card as Card;
            if (c != null)
            {
                c.CheckCaptureContact = null;
                c.HeapDispositionOffset = null;
            }

            this._cards.RemoveAt(index);

            switch (CardRemovingFace)
            {
                case CardInDeckFace.FrontUp:
                    if (!card.IsFront)
                        card.Turn();
                    break;
                case CardInDeckFace.BackUp:
                    if (card.IsFront)
                        card.Turn();
                    break;
                case CardInDeckFace.AsIs:
                    // don't do anything
                    break;
            }

            OnPropertyChanged(CountString);
            OnPropertyChanged(IndexerName);
            OnCollectionChanged(NotifyCollectionChangedAction.Remove, card, index);

            UpdateDisposition(true, true);

            card.Activate();
            card.ScatterView.Activate(card.ScatterViewItem);

            CheckAutoRemoveWhenEmpty();
        }

        public ICard this[int index]
        {
            get
            {
                return this._cards[index];
            }
            set
            {
                this._cards[index] = value;

                OnCollectionChanged(NotifyCollectionChangedAction.Replace, this._cards[index], index);
            }
        }

        #endregion

        #region ICollection<ICard> Members

        public void Add(ICard item)
        {
            CheckReentrancy();

            AddCard(item);
        }

        public void Clear()
        {
            CheckReentrancy();

            this._cards.Clear();

            OnPropertyChanged(CountString);
            OnPropertyChanged(IndexerName);
            OnCollectionReset();
        }

        public bool Contains(ICard item)
        {
            return this._cards.Contains(item);
        }

        public void CopyTo(ICard[] array, int arrayIndex)
        {
            this._cards.CopyTo(array, arrayIndex);
        }

        public int Count
        {
            get
            {
                return this._cards.Count;
            }
        }

        public bool IsReadOnly
        {
            get
            {
                //return _isReadOnly;
                return false;
            }
        }

        public bool Remove(ICard item)
        {
            CheckReentrancy();

            var index = this._cards.IndexOf(item);
            if (index != -1)
            {
                try
                {
                    RemoveAt(index);
                }
                catch
                {
                    Console.WriteLine("error");
                }
                return true;
            }
            else
                return false;

        }

        #endregion

        #region IEnumerable<ICard> Members

        public IEnumerator<ICard> GetEnumerator()
        {
            return this._cards.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return ((System.Collections.IEnumerable)this._cards).GetEnumerator();
        }

        #endregion

        #region Implementation of INotifyCollectionChanged

        #region SimpleMonitor
        protected IDisposable BlockReentrancy()
        {
            this._monitor.Enter();
            return this._monitor;
        }

        protected void CheckReentrancy()
        {
            //if ((this._monitor.Busy && (CollectionChanged != null)) && (CollectionChanged.GetInvocationList().Length > 1))
            //{
            //    throw new InvalidOperationException("Collection Reentrancy Not Allowed");
            //}
        }
        #endregion

        public event NotifyCollectionChangedEventHandler CollectionChanged;

        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (CollectionChanged != null)
            {
                using (BlockReentrancy())
                {
                    CollectionChanged(this, e);
                }
            }
        }

        private void OnCollectionChanged(NotifyCollectionChangedAction action, object item)
        {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item));
        }

        private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index)
        {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index));
        }

        private void OnCollectionReset()
        {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }

        #endregion

        #region Implementation of INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, e);
            }
        }

        private void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
                OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        #endregion

        #region IManipulableObject Members

        public ScatterViewItem ScatterViewItem
        {
            get
            {
                if (Board == null)
                    return null;

                return Board.ScatterView.ItemContainerGenerator.ContainerFromItem(this) as ScatterViewItem;
            }
        }

        public ScatterView ScatterView
        {
            get
            {
                return Board == null ? null : Board.ScatterView;
            }
        }

        public double Orientation
        {
            get { return (double)GetValue(OrientationProperty); }
            set { SetValue(OrientationProperty, value); }
        }
        public static readonly DependencyProperty OrientationProperty =
            DependencyProperty.Register("Orientation", typeof(double), typeof(Deck), new UIPropertyMetadata((double)0));

        public Point Center
        {
            get { return (Point)GetValue(CenterProperty); }
            set { SetValue(CenterProperty, value); }
        }
        public static readonly DependencyProperty CenterProperty =
            DependencyProperty.Register("Center", typeof(Point), typeof(Deck), new UIPropertyMetadata(new Point(double.NaN, double.NaN), OnCenterChanged));

        public Point ActualCenter
        {
            get
            {
                return (Point)base.GetValue(ActualCenterProperty);
            }
            set
            {
                base.SetValue(ActualCenterPropertyKey, value);
            }
        }
        private static readonly DependencyPropertyKey ActualCenterPropertyKey =
            DependencyProperty.RegisterReadOnly("ActualCenter", typeof(Point), typeof(Deck),
            new FrameworkPropertyMetadata(new Point(double.NaN, double.NaN), OnActualCenterChanged));
        public static readonly DependencyProperty ActualCenterProperty = ActualCenterPropertyKey.DependencyProperty;


        public double Width
        {
            get { return (double)GetValue(WidthProperty); }
            set { SetValue(WidthProperty, value); }
        }
        public static readonly DependencyProperty WidthProperty =
            DependencyProperty.Register("Width", typeof(double), typeof(Deck), new UIPropertyMetadata(0.0));

        public double Height
        {
            get { return (double)GetValue(HeightProperty); }
            set { SetValue(HeightProperty, value); }
        }
        public static readonly DependencyProperty HeightProperty =
            DependencyProperty.Register("Height", typeof(double), typeof(Deck), new UIPropertyMetadata(0.0));

        private Visibility _visibility;
        public Visibility Visibility
        {
            get { return _visibility; }
            set
            {
                if (_visibility != value)
                {
                    _visibility = value;
                    OnPropertyChanged("Visibility");
                }
            }
        }

        private Board _board;
        public Board Board
        {
            get { return _board; }
            set
            {
                if (_board != value)
                {
                    var oldSVI = ScatterViewItem;
                    _board = value;
                    var newSVI = ScatterViewItem;

                    OnScatterViewItemChanged(oldSVI, newSVI);
                }
            }
        }

        public double DefaultHeight
        {
            get { return _defaultHeight; }
            set
            {
                _defaultHeight = value;
                Height = _defaultHeight;

                // update deck width & height depending on DeckDisposition
                OnDeckDispositionChanged();
            }
        }

        public double DefaultWidth
        {
            get { return _defaultWidth; }
            set
            {
                _defaultWidth = value;
                Width = _defaultWidth;

                // update deck width & height depending on DeckDisposition
                OnDeckDispositionChanged();
            }
        }

        Point _lastCenterBeforeContactsUp;
        public Point LastCenterBeforeContactsUp
        {
            get { return _lastCenterBeforeContactsUp; }
            set
            {
                if (_lastCenterBeforeContactsUp != value)
                {
                    _lastCenterBeforeContactsUp = value;
                    OnPropertyChanged("LastCenterBeforeContactsUp");
                }
            }
        }

        #endregion

        #region Commands

        private DelegateCommand<CardInDeckFace> _changeCardAddingFaceCommand;
        public ICommand ChangeCardAddingFaceCommand
        {
            get
            {
                if (_changeCardAddingFaceCommand == null)
                {
                    _changeCardAddingFaceCommand = new DelegateCommand<CardInDeckFace>(
                        (cidf) => this.CardAddingFace = cidf);
                }

                return _changeCardAddingFaceCommand;
            }
        }


        private DelegateCommand<CardInDeckPosition> _changeCardAddingPositionCommand;
        public ICommand ChangeCardAddingPositionCommand
        {
            get
            {
                if (_changeCardAddingPositionCommand == null)
                {
                    _changeCardAddingPositionCommand = new DelegateCommand<CardInDeckPosition>(
                        (cidp) => this.CardAddingPosition = cidp);
                }

                return _changeCardAddingPositionCommand;
            }
        }

        private DelegateCommand<CardInDeckFace> _changeCardRemovingFaceCommand;
        public ICommand ChangeCardRemovingFaceCommand
        {
            get
            {
                if (_changeCardRemovingFaceCommand == null)
                {
                    _changeCardRemovingFaceCommand = new DelegateCommand<CardInDeckFace>(
                        (cidf) => this.CardRemovingFace = cidf);
                }

                return _changeCardRemovingFaceCommand;
            }
        }

        private DelegateCommand _beginCutCommand;
        public ICommand BeginCutCommand
        {
            get
            {
                if (_beginCutCommand == null)
                {
                    _beginCutCommand = new DelegateCommand(
                        () => this.BeginCut());
                }

                return _beginCutCommand;
            }
        }

        private DelegateCommand _beginSplitCommand;
        public ICommand BeginSplitCommand
        {
            get
            {
                if (_beginSplitCommand == null)
                {
                    _beginSplitCommand = new DelegateCommand(
                        () => this.BeginSplit());
                }

                return _beginSplitCommand;
            }
        }

        private DelegateCommand _sortCommand;
        public ICommand SortCommand
        {
            get
            {
                if (_sortCommand == null)
                {
                    _sortCommand = new DelegateCommand(
                        () => this.Sort());
                }

                return _sortCommand;
            }
        }

        private DelegateCommand _beginReturnCardsCommand;
        public ICommand BeginReturnCardsCommand
        {
            get
            {
                if (_beginReturnCardsCommand == null)
                {
                    _beginReturnCardsCommand = new DelegateCommand(
                        () => this.BeginReturnCards());
                }

                return _beginReturnCardsCommand;
            }
        }

        private DelegateCommand _turnAllCardsToFrontCommand;
        public ICommand TurnAllCardsToFrontCommand
        {
            get
            {
                if (_turnAllCardsToFrontCommand == null)
                {
                    _turnAllCardsToFrontCommand = new DelegateCommand(
                        () => this.TurnAllCards(true));
                }

                return _turnAllCardsToFrontCommand;
            }
        }

        private DelegateCommand _turnAllCardsToBackCommand;
        public ICommand TurnAllCardsToBackCommand
        {
            get
            {
                if (_turnAllCardsToBackCommand == null)
                {
                    _turnAllCardsToBackCommand = new DelegateCommand(
                        () => this.TurnAllCards(false));
                }

                return _turnAllCardsToBackCommand;
            }
        }

        private void TurnAllCards(bool isFront)
        {
            foreach (var card in this)
            {
                if (card.IsFront != isFront)
                    card.Turn();
            }
        }

        private DelegateCommand _changeDispositionToHeapCommand;
        public ICommand ChangeDispositionToHeapCommand
        {
            get
            {
                if (_changeDispositionToHeapCommand == null)
                {
                    _changeDispositionToHeapCommand = new DelegateCommand(
                        () => this.DeckDisposition = DeckDisposition.Heap);
                }

                return _changeDispositionToHeapCommand;
            }
        }

        private DelegateCommand _changeDispositionToStackCommand;
        public ICommand ChangeDispositionToStackCommand
        {
            get
            {
                if (_changeDispositionToStackCommand == null)
                {
                    _changeDispositionToStackCommand = new DelegateCommand(
                        () => this.DeckDisposition = DeckDisposition.Stack);
                }

                return _changeDispositionToStackCommand;
            }
        }

        #endregion

        private void CheckAutoRemoveWhenEmpty()
        {
            if (_isInitializing)
                return;

            if (AutoRemoveWhenEmpty && this.Count == 0)
                _board.RemoveDeck(this);
        }

        private void OnScatterViewItemChanged(ScatterViewItem oldValue, ScatterViewItem newValue)
        {
            if (oldValue != null)
            {
                oldValue.SizeChanged -= OnScatterViewItemSizeChanged;
                oldValue.Activated -= OnScatterViewItemActivated;
                oldValue.Deactivated -= OnScatterViewItemDeactivated;
                oldValue.PreviewContactDown -= OnScatterViewItemPreviewContactDown;
                oldValue.ScatterManipulationDelta -= OnScatterViewItemDelta;
                oldValue.ScatterManipulationStarted -= OnScatterViewItemStarted;
                DependencyPropertyDescriptor.FromProperty(ScatterViewItem.ActualCenterProperty, typeof(ScatterViewItem)).RemoveValueChanged(
                    oldValue, OnActualCenterChanged);
            }
            if (newValue != null)
            {
                newValue.SizeChanged += OnScatterViewItemSizeChanged;
                newValue.Activated += OnScatterViewItemActivated;
                newValue.Deactivated += OnScatterViewItemDeactivated;
                newValue.PreviewContactDown += OnScatterViewItemPreviewContactDown;
                newValue.ScatterManipulationStarted += OnScatterViewItemStarted;
                newValue.ScatterManipulationDelta += OnScatterViewItemDelta;
                DependencyPropertyDescriptor.FromProperty(ScatterViewItem.ActualCenterProperty, typeof(ScatterViewItem)).AddValueChanged(
                    newValue, OnActualCenterChanged);
            }
        }

        private Point _position;
        private Point _positionorigin;
        private int _numbershuffle;
        private void OnScatterViewItemStarted(object sender, ScatterManipulationStartedEventArgs e)
        {
            _position = e.ManipulationOrigin;
            _positionorigin = e.ManipulationOrigin;
            _numbershuffle = 0;
        }

        private void OnScatterViewItemDelta(object sender, ScatterManipulationDeltaEventArgs e)
        {
            double radiusOrigin = 10;
            double radiusOffset = 20;

            _position.Offset(e.HorizontalChange, e.VerticalChange);

            if ((_position.X > _positionorigin.X + radiusOffset
                || _position.X < _positionorigin.X - radiusOffset
                || _position.Y > _positionorigin.Y + radiusOffset
                || _position.Y < _positionorigin.Y - radiusOffset)
                && _numbershuffle % 2 == 0)
            {
                _numbershuffle++;
            }
            if ((_position.X < _positionorigin.X + radiusOrigin
                && _position.X > _positionorigin.X - radiusOrigin
                && _position.Y < _positionorigin.Y + radiusOrigin
                && _position.Y > _positionorigin.Y - radiusOrigin)
                && _numbershuffle % 2 == 1)
            {
                _numbershuffle++;
            }

            if (_numbershuffle >= 4)
            {
                _numbershuffle = 0;
                this.ScatterViewItem.CancelManipulation();
                this.Shuffle();
            }

        }

        private void OnScatterViewItemActivated(object sender, RoutedEventArgs e)
        {
            // Set it TopMost if needed
            if (!this.ScatterViewItem.IsTopmostOnActivation)
            {
                this.ScatterViewItem.SetRelativeZIndex(RelativeScatterViewZIndex.Topmost);
            }

            UpdateZIndexes();
        }

        private void OnScatterViewItemDeactivated(object sender, RoutedEventArgs e)
        {
            UpdateZIndexes();
        }

        private static void OnCenterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var deck = d as Deck;
            if (d == null)
                return;
            deck.OnCenterChanged();
        }

        private static void OnActualCenterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var deck = d as Deck;
            if (d == null)
                return;
            deck.OnActualCenterChanged();
        }

        private void OnActualCenterChanged(object sender, EventArgs e)
        {
            OnActualCenterChanged();
        }

        protected virtual void OnActualCenterChanged()
        {
            if (!_isInitializing)
                UpdateDisposition(false);
        }

        protected virtual void OnCenterChanged()
        {
            if (!_isInitializing)
                UpdateDisposition(false);
        }

        private void OnScatterViewItemPreviewContactDown(object sender, ContactEventArgs e)
        {
            var orientation = e.Contact.GetOrientation(this.ScatterViewItem);

            if (orientation > 70 && orientation < 110)
                SetDeckOrientation(DeckOrientation.Down);
            else if (orientation > 250 || orientation < 290)
                SetDeckOrientation(DeckOrientation.Up);
        }

        private void SetDeckOrientation(DeckOrientation orientation)
        {
            if (DeckOrientation != orientation)
            {
                DeckOrientation = orientation;
                UpdateDisposition(true, true);
            }
        }

        private void OnDeckDispositionChanged()
        {
            switch (DeckDisposition)
            {
                case DeckDisposition.Stack:
                    Disposition = _stackDisposition;
                    if (!(this.Disposition is FanDisposition))
                    {
                        Height = this.DefaultHeight;
                        if (this.ScatterViewItem != null)
                            this.ScatterViewItem.Width = this.DefaultWidth;
                    }
                    break;

                case DeckDisposition.Heap:
                    Disposition = _heapDisposition;
                    if (!(this.Disposition is FanDisposition))
                    {
                        Height = this.DefaultHeight + 35;
                        if (this.ScatterViewItem != null)
                            this.ScatterViewItem.Width = this.DefaultWidth + 50;
                    }
                    break;
            }

            UpdateDisposition(true);
        }

        private void OnCanApplyFanChanged()
        {
            if (this.Disposition is FanDisposition)
            {
                // set stack or heap disposition
                OnDeckDispositionChanged();
            }
        }

        public IDisposable DeferUpdateDisposition(bool useTransitions, bool sizeChanged)
        {
            _deferUpdateDispositionLevel++;
            return new DeferDispositionHelper(this, useTransitions, sizeChanged, 1);
        }

        public IDisposable DeferUpdateDisposition(bool useTransitions, bool sizeChanged, double durationRatio)
        {
            _deferUpdateDispositionLevel++;
            return new DeferDispositionHelper(this, useTransitions, sizeChanged, durationRatio);
        }

        private void EndDefer(bool useTransitions, bool sizeChanged, double durationRatio)
        {
            _deferUpdateDispositionLevel--;
            UpdateDisposition(useTransitions, sizeChanged, durationRatio);
        }

        private void OnScatterViewItemSizeChanged(object sender, SizeChangedEventArgs e)
        {
            OnSizeChanged();
        }

        protected virtual void OnSizeChanged()
        {
            RefreshDestinationRect();

            if (_isInitializing)
            {
                OnDeckDispositionChanged();
                _isInitializing = false;
            }

            double widthOffset;

            switch (DeckDisposition)
            {
                case DeckDisposition.Stack:
                    widthOffset = -9;
                    break;
                case DeckDisposition.Heap:
                    widthOffset = 21;
                    break;
                default:
                    // shouldn't go there
                    widthOffset = double.NaN;
                    break;
            }

            if (this.Disposition is FanDisposition)
            {
                if (_cardsDestinationRect.Width <= DefaultWidth + widthOffset ||
                !this.Disposition.CanApplyDisposition(DefaultCardSize, _cardsDestinationRect, this.Count))
                {
                    // set Stack or Heap
                    OnDeckDispositionChanged();
                    switch (DeckDisposition)
                    {
                        case DeckDisposition.Stack:
                            Disposition = _stackDisposition;
                            break;
                        case DeckDisposition.Heap:
                            Disposition = _heapDisposition;
                            break;
                    }
                }
            }
            else if ((this.Disposition is StackDisposition || this.Disposition is HeapDisposition) && CanApplyFan)
            {
                if (_cardsDestinationRect.Width > DefaultWidth + widthOffset &&
                _fanDisposition.CanApplyDisposition(DefaultCardSize, _cardsDestinationRect, this.Count))
                {
                    // set FAN
                    this.Disposition = _fanDisposition;
                    this.Height = this.DefaultHeight + 20;
                }
            }

            // Coerce width & height
            this.Width = this.ScatterViewItem.ActualWidth;
            this.ScatterViewItem.Height = this.Height;

            UpdateDisposition(true, true);
        }

        private void RefreshDestinationRect()
        {
            // calculate CardsDestinationRect

            if (GetCardsDestinationRect == null)
                throw new Exception("GetCardsDestinationRect can't be null");

            _cardsDestinationRect = GetCardsDestinationRect();
        }

        public void UpdateDisposition(bool useTransitions)
        {
            UpdateDisposition(useTransitions, false);
        }

        DispatcherOperation _updateDispositionInvoke;

        public void UpdateDisposition(bool useTransitions, bool sizeChanged)
        {
            UpdateDisposition(useTransitions, sizeChanged, 1);
        }

        public void UpdateDisposition(bool useTransitions, bool sizeChanged, double durationRatio)
        {
            if (_deferUpdateDispositionLevel != 0)
                return;

            if ((sizeChanged || !_isUpdatingDisposition) && this.ScatterViewItem != null)
            {
                _isUpdatingDisposition = true;

                UpdateZIndexes();

                RefreshDestinationRect();

                if (_cardsDestinationRect == new Rect(0, 0, 0, 0))
                {
                    _isUpdatingDisposition = false;
                    return;
                }

                if (_cardsDestinationRect == null)
                    throw new Exception("CardsDestinationRect must be set to place cards");

                if (this.Count > 0)
                {
                    if (_updateDispositionInvoke != null && _updateDispositionInvoke.Status == DispatcherOperationStatus.Pending)
                        _updateDispositionInvoke.Abort();

                    _updateDispositionInvoke = Dispatcher.BeginInvoke((Action)(() =>
                        {
                            if (this.ScatterViewItem != null)
                            {
                                Disposition.Render(
                                    this,
                                    DefaultCardSize,
                                    _cardsDestinationRect,
                                    this.ScatterViewItem.ActualCenter,
                                    double.IsNaN(this.ScatterViewItem.ActualOrientation) ? this.ScatterViewItem.Orientation : this.ScatterViewItem.ActualOrientation,
                                    useTransitions,
                                    sizeChanged,
                                   this.DeckOrientation,
                                   durationRatio);
                            }
                            _isUpdatingDisposition = false;
                        }
                    ), System.Windows.Threading.DispatcherPriority.Render);
                }

            }
        }

        private void UpdateZIndexes()
        {
            if (ScatterViewItem != null)
            {
                for (int i = Count - 1; i >= 0; i--)
                    this[i].ScatterViewItem.SetRelativeZIndex(RelativeScatterViewZIndex.Topmost);

                if (Count > 0)
                    Panel.SetZIndex(this.ScatterViewItem, Panel.GetZIndex(this[Count - 1].ScatterViewItem) - 1);
            }
        }

        internal void ToggleIsSelected(Card card)
        {
            if (Disposition is FanDisposition)
                card.IsSelected = !card.IsSelected;
        }

        #region Cut, Split & return cards

        internal void CheckCaptureContactForCard(Card c, ContactEventArgs e)
        {
            if (_isWaitingForCut)
            {
                Cut(c);
                e.Handled = true;
                e.Contact.Capture(this.ScatterViewItem);
            }
            else if (_isWaitingForSplit)
            {

                Split(c);
                e.Handled = true;
                e.Contact.Capture(this.ScatterViewItem);
            }
            else if (_isWaitingForReturnCards)
            {
                // capture the contact if it's the right one
                // if there is a contact on the deck yet
                if (ScatterViewItem.IsAnyContactCaptured && e.Contact.Captured != this && CanReturnAllCards)
                {
                    // capture this one 
                    e.Contact.Capture(this.ScatterViewItem.Content as IInputElement);
                    e.Handled = true;

                    _initialContactTurnCards = e.Contact.GetPosition(this.ScatterViewItem);

                    if (e.Contact.GetCenterPosition(this.ScatterViewItem).X >
                        ScatterViewItem.ContactsCaptured.First().GetCenterPosition(this.ScatterViewItem).X)
                    {
                        _cardsTurnDirection = CardsTurnDirection.Right;
                        _lastCardReturnedIndex = -1;
                    }
                    else
                    {
                        _cardsTurnDirection = CardsTurnDirection.Left;
                        _lastCardReturnedIndex = this.Count;
                    }
                }
            }
            else
            {
                if (CheckCaptureContactForCardsDelegate != null)
                    CheckCaptureContactForCardsDelegate(c, e);
            }
        }

        public void BeginCut()
        {
            _isWaitingForCut = true;
            _isWaitingForSplit = false;
            EndReturnCards();

            if (!(Disposition is FanDisposition))
            {
                this.ScatterViewItem.Width = this.ScatterViewItem.ActualWidth + this.Count * 20;
            }
        }

        public void Cut(Card c)
        {
            _isWaitingForCut = false;

            if (!this.Contains(c))
                return;

            if (!(this.Disposition is FanDisposition))
                return;

            var fanDisp = this.Disposition as FanDisposition;

            fanDisp.Cut(c);
        }


        public void BeginSplit()
        {
            _isWaitingForSplit = true;
            _isWaitingForCut = false;
            EndReturnCards();

            if (!(Disposition is FanDisposition))
            {
                this.ScatterViewItem.Width = this.ScatterViewItem.ActualWidth + this.Count * 20;
            }
        }

        public void Split(Card card)
        {
            _isWaitingForSplit = false;

            if (!this.Contains(card))
                return;

            var cardIndex = this.IndexOf(card);
            var cardsLeft = this.Where(c => card.Deck.IndexOf(c) < cardIndex).ToList();
            var cardsRight = this.Where(c => card.Deck.IndexOf(c) >= cardIndex).ToList();

            if (!cardsLeft.Any() || !cardsRight.Any())
                return;

            // create two decks
            using (this.DeferUpdateDisposition(true, true))
            {
                var leftCenter = this.First().Center;
                var rightCenter = this.Last().Center;
                this.Board.RemoveCardsFromDeck(this, cardsLeft);
                this.Board.RemoveCardsFromDeck(this, cardsRight);
                this.Game.CreateDeck(cardsLeft, leftCenter);
                this.Game.CreateDeck(cardsRight, rightCenter);
                this.Board.RemoveDeck(this);
            }

        }

        public void BeginReturnCards()
        {
            _isWaitingForReturnCards = true;
            _isWaitingForSplit = false;
            _isWaitingForCut = false;

            if (!(Disposition is FanDisposition))
            {
                this.ScatterViewItem.Width = this.ScatterViewItem.ActualWidth + this.Count * 20;
            }
        }

        public void TurnCards(IEnumerable<Card> cards, RotateDirection direction)
        {
            foreach (var card in cards)
            {
                card.Turn();
            }
        }

        internal void ComputeCardsNeededToBeReturned(Contact contact)
        {
            if (_isWaitingForReturnCards)
            {
                var thisPoint = contact.GetPosition(this.ScatterViewItem);

                IEnumerable<ICard> cardsBefore;
                switch (_cardsTurnDirection)
                {

                    case CardsTurnDirection.Left:
                        cardsBefore =
                            this.Where(c =>
                                this.ScatterView.TranslatePoint(c.ScatterViewItem.Center, this.ScatterViewItem).X > thisPoint.X &&
                                this.IndexOf(c) < _lastCardReturnedIndex)
                                .OrderByDescending(c => this.IndexOf(c))
                                .ToList();

                        if (cardsBefore.Any())
                        {
                            foreach (Card card in cardsBefore)
                            {
                                _lastCardReturnedIndex--;
                                card.Turn(RotateDirection.Left);
                                if (_lastCardReturnedIndex == 0)
                                    contact.Capture(null);
                            }
                        }

                        break;
                    case CardsTurnDirection.Right:

                        cardsBefore =
                            this.Where(c =>
                                this.ScatterView.TranslatePoint(c.ScatterViewItem.Center, this.ScatterViewItem).X < thisPoint.X &&
                                this.IndexOf(c) > _lastCardReturnedIndex).ToList();

                        if (cardsBefore.Any())
                        {
                            foreach (Card card in cardsBefore)
                            {
                                _lastCardReturnedIndex++;
                                card.Turn(RotateDirection.Right);
                                if (_lastCardReturnedIndex == this.Count)
                                    contact.Capture(null);
                            }
                        }

                        return;

                }
            }
        }

        enum CardsTurnDirection
        {
            Left,
            Right
        }

        public void EndReturnCards()
        {
            _isWaitingForReturnCards = false;
            _lastCardReturnedIndex = -1;
        }

        #endregion

        private class DeferDispositionHelper : IDisposable
        {
            private Deck _deck;
            private bool _useTransitions;
            private bool _sizeChanged;
            private double _durationRatio;

            public DeferDispositionHelper(Deck deck, bool useTransitions, bool sizeChanged, double durationRatio)
            {
                this._deck = deck;
                this._sizeChanged = sizeChanged;
                this._useTransitions = useTransitions;
                this._durationRatio = durationRatio;
            }

            public void Dispose()
            {
                if (_deck != null)
                {
                    _deck.EndDefer(_useTransitions, _sizeChanged, _durationRatio);
                    _deck = null;
                }
            }
        }
    }
}
